package com.xique.park.service.biz.service.impl;

import cn.easyes.core.biz.SAPageInfo;
import cn.easyes.core.conditions.LambdaEsQueryWrapper;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.net.URLEncoder;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.distance.DistanceUtils;
import com.xique.common.core.constant.Constants;
import com.xique.common.core.constant.HttpStatus;
import com.xique.common.core.utils.OrikaUtil;
import com.xique.common.core.web.domain.AjaxResult;
import com.xique.common.core.web.domain.PageResult;
import com.xique.park.bean.domain.*;
import com.xique.park.bean.dto.PayOrderDto;
import com.xique.park.bean.request.mini.OrderRequest;
import com.xique.park.bean.request.mini.SaveUserPlateRequest;
import com.xique.park.bean.request.mini.SelectNearByParkRequest;
import com.xique.park.bean.vo.mini.MiniParkInfo;
import com.xique.park.bean.vo.web.parkingOrder.ParkingOrderVo;
import com.xique.park.bean.vo.web.payOrder.PayOrderVo;
import com.xique.park.mapper.es.ParkingOrderEsMapper;
import com.xique.park.service.biz.service.IMiniProgramBizService;
import com.xique.park.service.biz.service.IParkSpaceBizService;
import com.xique.park.service.service.*;
import com.xique.park.utils.RedisUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author caogq
 */
@Service
public class MiniProgramBizServiceImpl implements IMiniProgramBizService {

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private IParkInfoService parkInfoService;

    @Autowired
    private IParkGarageService parkGarageService;

    @Autowired
    private IParkSpaceBizService parkSpaceBizService;

    @Autowired
    private IMiniUserCarService miniUserCarService;

    @Autowired
    private ParkingOrderEsMapper parkingOrderEsMapper;

    @Autowired
    private IPayOrderService payOrderService;

    @Autowired
    private IBaseUserService baseUserService;

    private SpatialContext spatialContext = SpatialContext.GEO;

    @Override
    public AjaxResult getNearByParkInfoRedis(SelectNearByParkRequest request) {
        if (StrUtil.isNotEmpty(request.getKeyWord())) {
            LambdaQueryWrapper<ParkInfo> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.like(ParkInfo::getName, request.getKeyWord());
            parkInfoService.list(queryWrapper);
            Page<ParkInfo> pageRequest = new Page<>(1, 10);
            Page<ParkInfo> pageInfo = parkInfoService.page(pageRequest, queryWrapper);
            //模糊查询先查询车场
            List<ParkInfo> parkInfos = pageInfo.getRecords();
            //
        }
        //查出所有附近所有的车场，限制50条
        List<ParkInfo> nearByAllParkInfos = redisUtil.getNearByParkInfo(request.getLongitude(), request.getLatitude(), request.getDistance());
        //手动分页
        Integer totalNum = nearByAllParkInfos.size();
        int pageNum = request.getPageNum();
        int pageSize = request.getPageSize();
        Integer totalPage = 0;
        if (totalNum > 0) {
            totalPage = totalNum % pageSize == 0 ? totalNum / pageSize : totalNum / pageSize + 1;
        }
        if (pageNum > totalPage) {
            pageNum = totalPage;
        }
        if (pageNum < 1) {
            pageNum = 1;
        }
        int startPoint = (pageNum - 1) * pageSize;
        int endPoint = startPoint + pageSize;
        if (totalNum <= endPoint) {
            endPoint = totalNum;
        }
        List<ParkInfo> pageResults = nearByAllParkInfos.subList(startPoint, endPoint);
        List<Long> ids = pageResults.stream().map(ParkInfo::getId).collect(Collectors.toList());
        List<ParkInfo> pageParkInfos = parkInfoService.listByIds(ids);

        List<MiniParkInfo> miniParkInfos = OrikaUtil.converts(pageResults, MiniParkInfo.class);

        //计算剩余车位
        calRemainSpace(miniParkInfos);

        return AjaxResult.success(miniParkInfos);
    }

    private List<MiniParkInfo> calRemainSpace(List<MiniParkInfo> miniParkInfos) {
        List<Long> parkIds = miniParkInfos.stream().map(MiniParkInfo::getId).collect(Collectors.toList());
        //查询所有车场的车库信息
        LambdaQueryWrapper<ParkGarage> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(ParkGarage::getParkId, parkIds);
        List<ParkGarage> garageList = parkGarageService.list(queryWrapper);

        for (MiniParkInfo parkInfo : miniParkInfos) {
            Long remainSpaces = 0L;
            List<ParkGarage> parkGarageList = garageList.stream().filter(e -> e.getParkId().equals(parkInfo.getId())).collect(Collectors.toList());
            for (ParkGarage parkGarage : parkGarageList) {
                remainSpaces += parkSpaceBizService.getGarageRemainSpaces(parkGarage);
            }
            parkInfo.setRemainSpaceNum(remainSpaces);
        }
        return miniParkInfos;
    }

    @Override
    public AjaxResult getNearByParkInfoMysql(SelectNearByParkRequest request) {

//        //1.根据要求的范围，确定geoHash码的精度，获取到当前用户坐标的geoHash码
//        int len = 1;
//        GeoHash geoHash = GeoHash.withCharacterPrecision(request.getLatitude(), request.getLongitude(), len);
//        //2.获取到用户周边8个方位的geoHash码
//        GeoHash[] adjacent = geoHash.getAdjacent();
//
//        LambdaQueryWrapper<ParkInfo> queryWrapper = new LambdaQueryWrapper<ParkInfo>()
////                .likeRight(ParkInfo::getGeoCode,geoHash.toBase32())
//                .eq(ParkInfo::getIsOpenPark,"Y")
//                .eq(StrUtil.isNotEmpty(request.getAreaCode()),ParkInfo::getCity,request.getAreaCode())
//                .and(StrUtil.isNotEmpty(request.getKeyWord()),qw-> qw.like(ParkInfo::getName,request.getKeyWord()).or().like(ParkInfo::getAddress,request.getKeyWord()));
//
//        Stream.of(adjacent).forEach(a -> queryWrapper.or().likeRight(ParkInfo::getGeoCode,a.toBase32()));

        // 1.V1：以上为过滤一定范围内的车场； V2：需求修改为不限制距离,直接查询所有车场
        LambdaQueryWrapper<ParkInfo> queryWrapper = new LambdaQueryWrapper<ParkInfo>()
                .eq(ParkInfo::getRecordStatus, Constants.NORMAL)
                .eq(StrUtil.isNotEmpty(request.getAreaCode()), ParkInfo::getCity, request.getAreaCode())
                .eq(2 == request.getOrderBy(), ParkInfo::getIsOpenPark, "Y")
                .and(StrUtil.isNotEmpty(request.getKeyWord()), qw -> qw.like(ParkInfo::getName, request.getKeyWord()).or().like(ParkInfo::getAddress, request.getKeyWord()));
        //2.匹配指定精度的geoHash码，找出范围内的车场
        List<ParkInfo> parkInfos = parkInfoService.list(queryWrapper);
        if (parkInfos.isEmpty()) {
            return AjaxResult.success("暂无信息");
        }

        //3.手动计算距离
        List<ParkInfo> list = new ArrayList<>();
        for (ParkInfo parkInfo : parkInfos) {
            if (parkInfo.getPositionX() == null || parkInfo.getPositionY() == null) {
                continue;
            }
            double distance1 = getDistance(parkInfo.getPositionX(), parkInfo.getPositionY(), request.getLongitude(), request.getLatitude());
            if (0 != request.getDistance() && distance1 > request.getDistance()) {
                //不是查询所有距离或者超出距离范围，跳过
                continue;
            }
            parkInfo.setDistance(distance1);
            list.add(parkInfo);
        }
        //4.手动排序
        list = list.stream().sorted(Comparator.comparing(ParkInfo::getDistance)).collect(Collectors.toList());
        //5.手动分页
        List<ParkInfo> pageList = list.stream().skip((request.getPageNum() - 1) * request.getPageSize()).limit(request.getPageSize()).collect(Collectors.toList());
        if (pageList.isEmpty()) {
            return AjaxResult.success("暂无信息");
        }

        List<MiniParkInfo> miniParkInfos = OrikaUtil.converts(pageList, MiniParkInfo.class);
        //计算剩余车位
        calRemainSpace(miniParkInfos);
        for (MiniParkInfo miniParkInfo : miniParkInfos) {
            if (StrUtil.isEmpty(miniParkInfo.getIsOpenPark())) {
                miniParkInfo.setIsOpenPark("N");
            }
            if (miniParkInfo.getRemainSpaceNum() != null && miniParkInfo.getRemainSpaceNum() < 0) {
                miniParkInfo.setRemainSpaceNum(0L);
            }
        }

        Map<String, Object> map = new HashMap<>();
        map.put("list", miniParkInfos);
        map.put("total", list.size());
        return AjaxResult.success(map);
    }

    @Override
    public AjaxResult saveUserPlate(SaveUserPlateRequest request) {
        LambdaQueryWrapper<MiniUserCar> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(MiniUserCar::getPlate, request.getPlate());
        List<MiniUserCar> list = miniUserCarService.list(queryWrapper);
        if (!list.isEmpty()) {
            return AjaxResult.error(HttpStatus.PLATE_EXISTED, "车牌被占用");
        }
        MiniUserCar miniUserCar = OrikaUtil.convert(request, MiniUserCar.class);
        miniUserCar.setCreateUser(request.getUserId());
        miniUserCar.setUpdateUser(request.getUserId());
        boolean flag = miniUserCarService.save(miniUserCar);
        return flag ? AjaxResult.success() : AjaxResult.error();
    }

    @Override
    public AjaxResult selectUserPlate(Long baseUserId) {
        LambdaQueryWrapper<MiniUserCar> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.eq(MiniUserCar::getUserId, baseUserId);
        List<MiniUserCar> list = miniUserCarService.list(queryWrapper);
        return AjaxResult.success(list);
    }

    @Override
    public AjaxResult deleteUserPlate(Long baseUserId, String plate) {
        LambdaQueryWrapper<MiniUserCar> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.eq(MiniUserCar::getUserId, baseUserId);
        queryWrapper.eq(MiniUserCar::getPlate, plate);
        MiniUserCar miniUserCar = miniUserCarService.getOne(queryWrapper);
        boolean flag = miniUserCarService.removeById(miniUserCar.getId());
        return flag ? AjaxResult.success() : AjaxResult.error();
    }

    @Override
    public PageResult selectOrderList(OrderRequest orderRequest) {
        LambdaEsQueryWrapper<ParkingOrder> queryWrapper = new LambdaEsQueryWrapper<>();
        queryWrapper.eq(null != orderRequest.getBaseUserId(), ParkingOrder::getBaseUserId, orderRequest.getBaseUserId());
        // 必须指定一种排序规则,且排序字段值必须唯一 此处用id进行排序 实际可根据业务场景自由指定,不推荐用创建时间,因为可能会相同
        queryWrapper.orderByDesc(ParkingOrder::getInTime);

        // 分页查询
        SAPageInfo<ParkingOrder> parkingOrderPageInfo;
        List<Object> nextSearchAfter;

        queryWrapper.size(orderRequest.getPageSize());

        List<Object> nextSearchAfterReq = orderRequest.getNextSearchAfter();
        if (CollUtil.isEmpty(nextSearchAfterReq)) {
            // 第一页
            parkingOrderPageInfo = parkingOrderEsMapper.searchAfterPage(queryWrapper, null, orderRequest.getPageSize());
        } else {
            // 下一页
            parkingOrderPageInfo = parkingOrderEsMapper.searchAfterPage(queryWrapper, nextSearchAfterReq, orderRequest.getPageSize());
        }
        nextSearchAfter = parkingOrderPageInfo.getNextSearchAfter();

        return handlePageResult(parkingOrderPageInfo, orderRequest, nextSearchAfter);
    }

    @Override
    public ParkingOrderVo selectOrderDetailById(Long id) {
        ParkingOrder parkingOrder = parkingOrderEsMapper.selectById(id);
        if (null != parkingOrder) {
            Long parkId = parkingOrder.getParkId();
            String parkingSerial = parkingOrder.getParkingSerial();
            ParkingOrderVo parkingOrderVo = BeanUtil.copyProperties(parkingOrder, ParkingOrderVo.class);

            // 查询支付记录
            List<PayOrderVo> payOrderVoList = selectPayOrderByParkingSerial(parkId, parkingSerial);
            parkingOrderVo.setPayOrderList(payOrderVoList);

            // 计算实际支付费用
            BigDecimal totalActualFee = BigDecimal.ZERO;
            if (CollUtil.isNotEmpty(payOrderVoList)) {
                for (PayOrderVo payOrderVo : payOrderVoList) {
                    BigDecimal actualFee = payOrderVo.getActualFee();
                    if (null == actualFee) {
                        actualFee = BigDecimal.ZERO;
                    }
                    totalActualFee = totalActualFee.add(actualFee);
                }
            }
            parkingOrderVo.setTotalActualFee(totalActualFee);

            return parkingOrderVo;
        }
        return new ParkingOrderVo();
    }

    private List<PayOrderVo> selectPayOrderByParkingSerial(Long parkId, String parkingSerial) {
        List<PayOrderDto> payOrderDtoList = payOrderService.selectPayOrderByParkingSerial(parkId, parkingSerial);
        List<PayOrderVo> payOrderVoList = new ArrayList<>();
        if (payOrderDtoList != null && !payOrderDtoList.isEmpty()) {
            payOrderVoList = payOrderDtoList.stream().map(payOrderDto -> PayOrderVo.builder()
                            .payType(payOrderDto.getPayType())
                            .payTime(payOrderDto.getPayTime())
                            .actualFee(payOrderDto.getActualFee())
                            .paySsn(payOrderDto.getPaySsn())
                            .build())
                    .collect(Collectors.toList());
        }
        return payOrderVoList;
    }

    private PageResult handlePageResult(SAPageInfo<ParkingOrder> parkingOrderPageInfo, OrderRequest orderRequest, List<Object> nextSearchAfter) {
        List<ParkingOrder> parkingOrderList = parkingOrderPageInfo.getList();
        return PageResult.builder()
                .pageNum(orderRequest.getPageNum())
                .pageSize(orderRequest.getPageSize())
                .totalPages((long) parkingOrderPageInfo.getPages())
                .totalSize(parkingOrderPageInfo.getTotal())
                .nextSearchAfter(nextSearchAfter)
                .list(parkingOrderList)
                .build();
    }

    /***
     * 球面中，两点间的距离
     * @param longitude 经度1
     * @param latitude  纬度1
     * @param userLng   经度2
     * @param userLat   纬度2
     * @return 返回距离，单位km
     */
    private double getDistance(Double longitude, Double latitude, double userLng, double userLat) {
        return spatialContext.calcDistance(spatialContext.makePoint(userLng, userLat),
                spatialContext.makePoint(longitude, latitude)) * DistanceUtils.DEG_TO_KM;
    }

    @Value("${invoice.appSecret}")
    public String appSecret;

    @Value("${invoice.code}")
    public String code;

    @Value("${invoice.url}")
    public String invoiceUrl;

    @Value("${invoice.path}")
    public String invoicePath;

    @Override
    public AjaxResult invoice(Long id) {
        if (ObjectUtil.isEmpty(id)) {
            return AjaxResult.error("订单id不能为空");
        }
        ParkingOrder parkingOrder = parkingOrderEsMapper.selectById(id);
        if (ObjectUtil.isEmpty(parkingOrder)) {
            return AjaxResult.error("未找到对应的订单信息");
        }
        Long parkId = parkingOrder.getParkId();
        ParkInfo parkInfo = parkInfoService.selectParkInfoById(parkId);
        if (ObjectUtil.isEmpty(parkInfo)) {
            return AjaxResult.error("未找到对应的项目信息");
        }
        if (StrUtil.isEmpty(parkInfo.getInvoiceOrgCode())) {
            return AjaxResult.error("该项目未开通开票业务");
        }

        String orderId = parkingOrder.getParkingSerial();
        Long amount = parkingOrder.getTotalFee();
        Long orderTime = parkingOrder.getUpdateTime().getTime() / 1000;
        String ordCode = parkInfo.getInvoiceOrgCode();

        String orgStr = orderId + "#" + amount + "#" + orderTime + "#" + ordCode;
        String md5Str = DigestUtils.md5DigestAsHex((orgStr + appSecret).getBytes()).toUpperCase();
        String urlEncodeStr = URLEncoder.createDefault().encode(orgStr + "#" + md5Str, StandardCharsets.UTF_8);
        String url = invoiceUrl + code + "/" + urlEncodeStr;
        String path = invoicePath + "?q=" + URLEncoder.createDefault().encode(url, StandardCharsets.UTF_8);
        return AjaxResult.success("成功", path);
    }

    @Override
    public AjaxResult getPushSetting(Long userId) {
        if (ObjectUtil.isEmpty(userId)) {
            return AjaxResult.error("未找到对应的用户信息");
        }
        BaseUser baseUser = baseUserService.selectBaseUserById(userId);
        Integer pushStatus = baseUser.getPushStatus();
        return AjaxResult.success("成功", pushStatus);
    }

    @Override
    public AjaxResult settingPush(Long userId, Integer status) {
        if (ObjectUtil.isEmpty(userId)) {
            return AjaxResult.error("未找到对应的用户信息");
        }
        if (ObjectUtil.isEmpty(status)) {
            return AjaxResult.error("请传入状态值");
        }
        BaseUser user = new BaseUser();
        user.setId(userId);
        user.setPushStatus(status);
        int count = baseUserService.updateBaseUser(user);
        if (count <= 0) {
            return AjaxResult.error("设置失败");
        }
        return AjaxResult.success("设置成功");
    }
}
